Tagpy - XML/HTML tag generator documentation

Design notes

Purpose is to create a simple html generator for Python. Few other generators have been available since decade but they all seems to have small caveats. However combining features I've managed to create a library that fits better to my own projects.

Problems:

  1. Some libraries use awkward CAPITAL letters, convention derived from early age of internet. I prefer small letters on tag and attribute names as specified on xhtml standard.
  2. Reserved words in Python language limits using some tag and tag attribute names. This has been tackled by allowing usage of CAPITAL letters on helper interface, but on the background letters are forced to lowercase. This however can be passed by using tag.setName and tag.setAttribute -methods. In this manner you can set any html standard tag and attribute name on the document like <my-tag dc:attr="" />
  3. I don't want to limit tags to html4 tag names only, but allow practically any tag name. See above.
  4. Sometimes content is transformed to html entities, which should not occur until very end of the business logic.

Other requirements:

As simple implementation as possible, no need for complicated page generation methods, just basic functionality. Extending tags for structures like table and lists. Nesting tags and giving attribute names should be clean and intuitive. Pythonic.

Base class


In [1]:
class TAG(object):
    def __init__(self, *args, **kw):
        pass

    def getName(self):
        pass
    def setName(self, name):
        pass

    def getAttribute(self, key):
        pass
    def setAttribute(self, key, value):
        pass

    def rcontent(self, item):
        pass
    def content(self, item):
        pass

    def prepend(self, item):
        pass
    def append(self, item):
        pass

Helper


In [2]:
class htmlHelper(object):
    def __getattr__(self, tag):
        pass

Table


In [3]:
def table(*args, **kw):
    global helper
    class table(type(helper.table())):
        def __init__(self, *args, **kw):
            pass
        
        def addCaption(self, caption, **kw):
            pass
        
        def addColGroup(self, *cols, **kw):
            pass
        
        def addHeadRow(self, *trs, **kw):
            pass
        
        def addFootRow(self, *trs, **kw):
            pass
        
        def addBodyRow(self, *trs, **kw):
            pass
        
        def addBodyRows(self, *trs, **kw):
            pass
    
    return table(*args, **kw)

helper = htmlHelper()

Usage


In [4]:
from tagpy import helper as h

In [5]:
# introducing the main flow of the nesting tags
print h.html(h.head(h.title("Simple html document")), h.body("Content"))


<html><head><title>Simple html document</title></head><body>Content</body></html>

In [6]:
# a tag without content will be output as a short tag form
print h.br()


<br/>

In [7]:
# if you pass empty string on tag content, closing tag will be generated
print h.script('')


<script></script>

In [8]:
# content can be a string, a numeric or other tag elements
print h.h1("Header ", 1, h.span(".1"))


<h1>Header 1<span>.1</span></h1>

In [9]:
# providing other content can yield unexpected results because all will be string normalized
print h.div([0,1], {'k': h.b()})


<div>[0, 1]{'k': <tagpy.main.b object at 0x102fd4b10>}</div>

In [10]:
# content can be callable
print h.p(h.br)


<p><br/></p>

In [11]:
# as said, content can be callable
def ul():
    return h.ul(h.li)
print h.div(ul)


<div><ul><li/></ul></div>

In [12]:
# adding more content inside the element
# special operator here << is same as tag.content method
h1 = h.h1('Header')
h1 << " 1."
h1 << h.span("2")
print h1


<h1>Header 1.<span>2</span></h1>

In [13]:
# adding more content inside the element
# special operator here >> is same as tag.rcontent method (reverse side content)
h1 = h.h1('Header')
h1 >> ".2 "
h1 >> h.span("1")
print h1


<h1><span>1</span>.2 Header</h1>

In [14]:
# see also
print h.a() << h.b() << h.c()
print h.a() >> h.b() >> h.c()


<a><b/><c/></a>
<a><c/><b/></a>

In [15]:
# adding more content outside the element, right side
h1 = h.h1()
h1 += h.h2() #h1 = h1 + h.h2()
h1 += h.h3() #h1 = h1 + h.h3()
print h1


<h1/><h2/><h3/>

In [16]:
# adding more content outside the element, left side
h1 = h.h1()
h1 = h.h2() + h1
h1 = h.h3() + h1
print h1


<h3/><h2/><h1/>

In [17]:
# chain add elements
print h.h1() + h.h2() + h.h3()
# note that adding instance to the same instance causes recursive loop on string normalization
#h1 = h.h1()
#print h1 + h1 + h1 -> Runtime error


<h1/><h2/><h3/>

In [18]:
# chain arguments
print h.h1(h.span(), h.span(), h.span())


<h1><span/><span/><span/></h1>

In [19]:
# chain arguments by list
print h.h1(*[h.span, h.span, h.span])


<h1><span/><span/><span/></h1>

In [20]:
# add attributes
print h.div(id="container", title="Content container")


<div id="container" title="Content container"/>

In [21]:
# add attributes by dictionary
print h.div(**{'id': "container", 'title':"Content container"})


<div id="container" title="Content container"/>

In [22]:
# using python reserved words can be tackled with uppercase letters or capitalization
# h.del or h.tag(class="") doesn't work but gives parse error. instead use something like:
print h.DEL(Class="reserved")


<del class="reserved"/>

In [23]:
# but if you really want uppercase tag names or attributes, you can always use setName and setAttribute methods
print h.tag().setName('DEL').setAttribute('Class', 'reserved')


<DEL Class="reserved"/>

In [24]:
# special attribute and tag names can be handled with setters.
# h.my-tag(dc:name = "special") doesn't work because of naming convention rules on python
# so you need to do:
print h.tag("content").setName('my-tag').setAttribute('dc:name', 'special')


<my-tag dc:name="special">content</my-tag>

Table extension class


In [25]:
# sure you can make tables with core table tags
tbl = h.table(CLASS="data")
tbl << h.thead(h.tr(*map(h.th, [1,2,3])))
tbl << h.tbody(*[h.tr(*map(h.td, ["1.%s"%i,"2.%s"%i,"3.%s"%i])) for i in [1,2,3]])

print tbl


<table class="data"><thead><tr><th>1</th><th>2</th><th>3</th></tr></thead><tbody><tr><td>1.1</td><td>2.1</td><td>3.1</td></tr><tr><td>1.2</td><td>2.2</td><td>3.2</td></tr><tr><td>1.3</td><td>2.3</td><td>3.3</td></tr></tbody></table>

In [26]:
from IPython.display import HTML
HTML(str(tbl))


Out[26]:
123
1.12.13.1
1.22.23.2
1.32.33.3

In [27]:
# but using special table factory function structuring table is easier
from tagpy import table

# initialize table
t = table(**{'id': 'data'})

# add caption title
t.addCaption('Caption')

columns = [{'style': 'background-color: red'},
           {'style': 'background-color: green'},
           {'style': 'background-color: blue'}]

# add column definitions
t.addColGroup(*[h.col(**attr) for attr in columns])

header = ['Column 1', 'Column 2', 'Column 3']

# add header row with column titles
t.addHeadRow(h.tr(*map(h.th, header)))

# add body rows
for i in range(1,3):
    t.addBodyRow(h.tr(*map(h.td, ["1.%s"%i,"2.%s"%i,"3.%s"%i])))

# add separate bodies with rows
for i in range(3,5):
    t.addBodyRows(h.tr(*map(h.td, ["1.%s"%i,"2.%s"%i,"3.%s"%i])), id='tbody%s'%i)

# add footer row
t.addFootRow(h.tr(h.td('footer', colspan="3")))

HTML(str(t))


Out[27]:
Caption
Column 1Column 2Column 3
footer
1.12.13.1
1.22.23.2
1.32.33.3
1.42.43.4

Some styles for table


In [28]:
style = """
<style type="text/css">
table#data { margin: 1em auto; border-collapse: collapse; border: 0} 
table#data caption { font-size: 1.2em; text-align: center; padding: 3px} 
table#data th, table#data td { padding: .25em; border: 1px solid #000; font-family: sans-serif; color: white} 
table#data th { color: #004900; font-weight: bold; text-align: left; } 
table#data thead th { border-bottom: 3px double #000; background-color: #ddd; text-align: center; } 
table#data tfoot td { border-top: 3px double #000; color: #fff; font-style: italic; font-size: .8em; text-align: center; background-color: brown} 
table#data tbody th { color: #000; }
table#data #tbody3 {font-weight: bold;}
table#data #tbody4 {font-style: italic;}
</style>
"""
HTML(style)


Out[28]:

Tests

See test cases notebook.

The MIT License (MIT)

Copyright (c) 2014 Marko Manninen

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.